/*
 * Copyright (c) 2010 Vadim Zhukov <persgray@gmail.com>
 *
 * Permission to use, copy, modify, and distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */


#include "common.h"

#include <sys/param.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <sys/wait.h>

#include <netinet/in.h>

#include <assert.h>
#include <err.h>
#include <errno.h>
#include <fcntl.h>
#include <fnmatch.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <time.h>
#include <unistd.h>

#include "logmsg.h"
#include "resolver.h"
#include "sensor.h"


#define s_buf		s->sp.sp_log_stream.buf
#define s_bufpos	s->sp.sp_log_stream.bufpos
#define s_addr		s->sp.sp_log_stream.addr
#define s_family	s->sp.sp_log_stream.family
#define s_truncated 	s->sp.sp_log_stream.truncated

#define sls_connected(s)	(s->fd != -1)


static int	sensor_log_stream_connect(struct wsensor *);
static void	sensor_log_stream_cleanup(struct wsensor *);
static int	sensor_log_stream_fill_buffer(struct wsensor *);


int
sensor_log_stream_connect(struct wsensor *s) {
	struct bindaddr	ba;
	int		oerrno;

	assert(!sls_connected(s));

	if (fill_sa_from_user(s_addr, &ba, 0, NULL) == -1)
		goto err;
	if (ba.ss.ss_family != s_family) {
		errno = EINVAL;
		goto err;
	}

	s->fd = socket(s_family, SOCK_STREAM, 0);
	if (s->fd == -1) {
		DPRINTF(1, "sensor_log_stream_connect: socket");
		goto err;
	}
	if (fcntl(s->fd, F_SETFL, O_NONBLOCK) == -1) {
		DPRINTF(1, "sensor_log_stream_connect: fcntl");
		goto err;
	}
	if (connect(s->fd, (const struct sockaddr *)&ba.ss, ba.slen) == -1) {
		DPRINTF(1, "sensor_log_stream_connect: connect");
		goto err;
	}

	return 0;

err:
	oerrno = errno;
	sensor_log_stream_cleanup(s);
	errno = oerrno;
	return -1;
}

void
sensor_log_stream_cleanup(struct wsensor *s) {
	if (s->fd != -1) {
		close(s->fd);
		s->fd = -1;
	}
}

/*
 * XXX: Maybe ring buffer will be better?..
 */
int
sensor_log_stream_fill_buffer(struct wsensor *s) {
	ssize_t	rlen;
	char	rbuf[sizeof(s_buf)];

	if (!sls_connected(s))
		if (sensor_log_stream_connect(s) == -1)
			return -1;

	rlen = read(s->fd, rbuf, s_bufpos);
	if (rlen == -1) {
		if (errno != EAGAIN && errno != EINTR)
			sensor_log_stream_cleanup(s);
		return -1;
	}
	memmove(&s_buf[s_bufpos - rlen], &s_buf[s_bufpos],
	    sizeof(s_buf) - s_bufpos);
	memcpy(&s_buf[sizeof(s_buf) - rlen], rbuf, rlen);
	s_bufpos -= rlen;
	return 0;
}

int
sensor_log_stream_init(struct wsensor *s) {
	const char	*type;

	/*
	 * Name always starts with "/sensor/". In this case it should be in
	 * the following form: "/sensor/"
	 *
	 * I
	 */
	type = s->name + 8;
	s_addr = strchr(type, '/');
	if (s_addr == NULL) {
		DPRINTFX(1, "sensor_log_stream_init: stream type unspecified");
		errno = EINVAL;
		return -1;
	}

	if (strncmp(type, "unix", s_addr - type) == 0 ||
	    strncmp(type, "local", s_addr - type) == 0)
		s_family = AF_LOCAL;
	else if (strncmp(type, "tcp", s_addr - type) == 0)
		s_family = AF_INET;
	else if (strncmp(type, "tcp6", s_addr - type) == 0)
		s_family = AF_INET6;
	else {
		errno = EPFNOSUPPORT;
		DPRINTF(1, "sensor_log_stream_init");
		return -1;
	}

	/* Ignore errors, stream source could be just temporary unavailable */
	(void)sensor_log_stream_connect(s);

	return 0;
}

int
sensor_log_stream_uninit(struct wsensor *s) {
	sensor_log_stream_cleanup(s);
	return 0;
}

int
sensor_log_stream_get(struct wsensor *s, struct log_msg *m) {
	size_t	 end, len;
	int	 rv;

	/* Be careful here with preserving errno value */

	rv = 0;
read_buf:
	if (s_bufpos > 0)
		/* Just save errcode, and try to read existing data first */
		rv = sensor_log_stream_fill_buffer(s);

	for (end = s_bufpos; end < sizeof(s_buf); end++)
		if (s_buf[end] == '\n')
			break;

	if (s_truncated) {
		/* Ignore text until next newline */
		s_bufpos = end;
		if (rv == -1)		/* Already failed */
			return -1;
		if (end != sizeof(s_buf))
			s_truncated = 0;
		goto read_buf;
	}

	if (end == sizeof(s_buf)) {
		if (s_bufpos > 0) {
			/* Maybe line just is not received fully yet */
			if (rv != -1)
				errno = EAGAIN;
			return -1;
		}
		DPRINTFX(2, "sensor_log_stream_get: line truncated");
		s_truncated = 1;
	}

	len = end - s_bufpos;
	rv = sensor_convert_log_line(&s_buf[s_bufpos], len, m);
	if (end < sizeof(s_buf) && s_buf[end] == '\n')
		end++;
	s_bufpos = end;
	return rv;
}
